package vg.implementation.ll_services.data_base_service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang.Validate;
import vg.interfaces.ll_services.data_base_service.IGraphDataBaseService;
import vg.interfaces.ll_services.data_base_service.data.graph.Attribute;
import vg.interfaces.ll_services.data_base_service.data.graph.Edge;
import vg.interfaces.ll_services.data_base_service.data.graph.Graph;
import vg.interfaces.ll_services.data_base_service.data.graph.Vertex;
import vg.interfaces.ll_services.data_base_service.data.header.*;

import java.util.*;

/**
 * This class realizes model, which uses SQL data base.
 * This model may cache graph elements in operating memory.
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class SQLiteCacheGraphDataBaseService implements IGraphDataBaseService {
	// database data
	private SQLiteCacheDataBase sqliteCacheDataBase;

    // Mutex
    private final Object generalMutex = new Object();

	public SQLiteCacheGraphDataBaseService() {
        sqliteCacheDataBase = new SQLiteCacheDataBase();
	}
	
	@Override
	public int createGraphModelHeader(String name)  {
        synchronized (generalMutex) {
    		return sqliteCacheDataBase.createGraphModelHeader(name);
        }
	}
	
	@Override
	public int createGraphHeader(int graphModelId, boolean directed, boolean root) {
		synchronized (generalMutex) {
            return sqliteCacheDataBase.createGraphHeader(graphModelId, directed, root);
        }
	}
	
	@Override
	public int createVertexHeader(int graphId, int innerGraphId) {
		synchronized (generalMutex) {
            return sqliteCacheDataBase.createVertexHeader(graphId, innerGraphId);
        }
	}
	
	@Override
	public int createEdgeHeader(int graphId, int sourceVertexId, int targetVertexId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.createEdgeHeader(graphId, sourceVertexId, targetVertexId);
        }
	}
	
	@Override
	public int createEdgeAttributeHeader(int edgeId, String name, String value, int type) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.createEdgeAttributeHeader(edgeId, name, value, type);
        }
	}
	
	@Override
	public int createVertexAttributeHeader(int vertexId, String name, String value, int type) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.createVertexAttributeHeader(vertexId, name, value, type);
        }
	}

    @Override
    public int createGraphAttributeHeader(int graphId, String name, String value, int type) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.createGraphAttributeHeader(graphId, name, value, type);
        }
    }

    @Override
    public void modifyVertexHeader(VertexHeader newVertexHeader) {
        synchronized (generalMutex) {
            sqliteCacheDataBase.modifyVertexHeader(newVertexHeader);
        }
    }

    @Override
    public List<GraphModelHeader> getGraphModelHeaders(int count) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getGraphModelHeaders(count);
        }
    }

    @Override
    public VertexHeader getVertexHeader(int vertexId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getVertexHeader(vertexId);
        }
    }

    @Override
    public List<VertexHeader> getVertexHeadersByGraphId(int graphId, int count) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getVertexHeadersByGraphId(graphId);
        }
    }

    @Override
    public EdgeHeader getEdgeHeader(int edgeId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getEdgeHeader(edgeId);
        }
    }

    @Override
    public List<EdgeHeader> getEdgeHeadersByGraphId(int graphId, int count) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getEdgeHeadersByGraphId(graphId);
        }
    }

    @Override
    public GraphHeader getGraphHeader(int graphId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getGraphHeader(graphId);
        }
    }

    @Override
    public List<GraphHeader> getRootGraphHeadersByGraphModelId(int graphModelId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getRootGraphHeadersByGraphModelId(graphModelId);
        }
    }

    @Override
    public GraphModelHeader getGraphModelHeader(int graphModelId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getGraphModelHeader(graphModelId);
        }
    }

    @Override
    public AttributeHeader getAttributeHeader(int attributeId) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getAttributeHeader(attributeId);
        }
    }

    @Override
    public List<AttributeHeader> getAttributeHeadersByOwner(int ownerId, int ownerType) {
        synchronized (generalMutex) {
            return sqliteCacheDataBase.getAttributeHeadersByOwner(ownerId, ownerType);
        }
    }

    @Override
    public Vertex getVertex(int vertexId) {
        synchronized (generalMutex) {
            VertexHeader vertexHeader = getVertexHeader(vertexId);
            Vertex vertex = new Vertex(getAttributesByOwner(vertexHeader.getId(), AttributeHeader.VERTEX_OWNER_TYPE));
            if (vertexHeader.getLink2InnerGraph() >= 0) {
                Graph graph = new Graph(false);
                graph.setStorableId(vertexHeader.getLink2InnerGraph());
                vertex.setInnerGraph(graph);
            }
            vertex.setStorableId(vertexHeader.getId());
            return vertex;
        }
    }

	@Override
	public Edge getEdge(int edgeId) {
        synchronized (generalMutex) {
            EdgeHeader edgeHeader = getEdgeHeader(edgeId);
            return new Edge(getVertex(edgeHeader.getSourceId()), getVertex(edgeHeader.getTargetId()), getAttributesByOwner(edgeHeader.getId(), AttributeHeader.EDGE_OWNER_TYPE));
        }
	}

    @Override
    public Graph getGraph(int graphId) {
        synchronized (generalMutex) {
            GraphHeader graphHeader = getGraphHeader(graphId);

            Map<Integer, Vertex> vertices = getVertices(graphId);
            Map<Integer, Edge> edges = getEdges(graphId, vertices);

            Graph graph = new Graph(vertices.values(), edges.values(), graphHeader.isDirected());
            graph.setStorableId(graphHeader.getId());

            return graph;
        }
    }

    @Override
    public Attribute getAttribute(int attributeId) {
        synchronized (generalMutex) {
            return convertToAttribute(getAttributeHeader(attributeId));
        }
    }

    @Override
    public void close() {
        synchronized (generalMutex) {
            sqliteCacheDataBase.close();
        }
    }

//==============================================================================
//------------------PRIVATE METHODS---------------------------------------------
    private Map<Integer, Vertex> getVertices(int graphId) {
        Map<Integer, Vertex> vertices = Maps.newHashMap();
        List<VertexHeader> vertexHeaders = sqliteCacheDataBase.getVertexHeadersByGraphId(graphId);
        for (VertexHeader vertexHeader : vertexHeaders) {
            vertices.put(vertexHeader.getId(), getVertex(vertexHeader.getId()));
        }
        return vertices;
    }

    private Map<Integer, Edge> getEdges(int graphId, Map<Integer, Vertex> vertices) {
        Map<Integer, Edge> edges = Maps.newHashMap();
        List<EdgeHeader> edgeHeaders = sqliteCacheDataBase.getEdgeHeadersByGraphId(graphId);

        for (EdgeHeader edgeHeader : edgeHeaders) {
            Vertex src = vertices.get(edgeHeader.getSourceId());
            Vertex trg = vertices.get(edgeHeader.getTargetId());
            Edge edge = new Edge(src, trg, getAttributesByOwner(edgeHeader.getId(), AttributeHeader.EDGE_OWNER_TYPE));
            edge.setStorableId(edgeHeader.getId());
            edges.put(edgeHeader.getId(), edge);
        }

        return edges;
    }

    private List<Attribute> getAttributesByOwner(int ownerId, int ownerType) {
        List<AttributeHeader> attributeHeaders = sqliteCacheDataBase.getAttributeHeadersByOwner(ownerId, ownerType);
        return convertToAttributes(attributeHeaders);
    }

    private static Attribute convertToAttribute(AttributeHeader attributeHeader) {
        Validate.notNull(attributeHeader);

        Attribute attribute = new Attribute(attributeHeader.getName(), attributeHeader.getStringValue(), attributeHeader.getType());
        attribute.setStorableId(attributeHeader.getId());
        return attribute;
    }

    private static List<Attribute> convertToAttributes(List<AttributeHeader> attributeHeaders) {
        Validate.notNull(attributeHeaders);

        List<Attribute> result = Lists.newArrayList();
        for (AttributeHeader header : attributeHeaders) {
            result.add(convertToAttribute(header));
        }

        return result;
    }
}
